Design - Paradigms
About
-
It is the structural implementation of a program.
-
"Ways of managing states in a program".
Imperative
-
Based on giving instructions to the computer to modify the program’s state.
-
The focus is on how things should be done, using sequential commands, loops, and variable assignments.
-
Subsets :
-
Procedural.
-
OOP.
-
-
Examples :
-
Assembly, C, Python (when used imperatively).
-
Declarative
-
Focuses on what should be done, not how. The programmer describes the desired results without specifying the exact steps.
-
Subsets :
-
Functional.
-
Logical.
-
Database.
-
-
Examples :
-
HTML, CSS.
-
SQL, LINQ.
-
Procedural
About
-
The Procedural Paradigm follows the Imperative Paradigm but organizes code into procedures (or functions).
Purely Procedural Languages
-
Today, there is almost nothing “purely procedural.”
-
Fortran :
-
Although it evolved over time and gained modern features, the original Fortran was strictly procedural, focusing on functions that manipulated data.
-
-
Algol :
-
One of the first languages to adopt a purely procedural style.
-
Concept of "objects" in the Procedural Paradigm
-
Talking about "procedural implementation for objects" creates a contradiction , since objects imply an object-oriented paradigm (encapsulation, inheritance, polymorphism, etc.).
-
Procedural programming focuses on organizing code around functions that operate on data.
-
Code is organized into functions (or procedures) and control flow, without object-oriented abstractions like classes or inheritance.
Applying the Procedural Paradigm
In C
-
C was designed to be procedural , meaning it relies on functions and procedures (or subroutines) to manipulate data and control execution flow. C code is organized around functions operating on variables and data structures, with flow controlled by loops (
for,while), conditionals (if,else), and function calls. -
However, C is not purely procedural for several reasons:
-
Use of data structures :
-
C allows
structs to group data, resembling encapsulation, often seen in OOP.
-
-
Macros and preprocessing :
-
The preprocessor (
#define, directives) allows abstraction beyond purely procedural limits.
-
-
Pointers and memory manipulation :
-
Direct memory manipulation provides abstraction capabilities similar to some OOP patterns.
-
-
-
Examples :
-
Procedural :
#include <stdio.h> int somar(int a, int b) { return a + b; } int main() { int x = 5; int y = 10; int resultado = somar(x, y); printf("Resultado da soma: %d\n", resultado); return 0; }-
"OOP" :
#include <stdio.h> typedef struct { char rua[100]; char cidade[50]; char estado[50]; } Endereco; typedef struct { char nome[50]; int idade; Endereco endereco; } Pessoa; void imprimir_endereco(const Endereco *e) { printf("Endereço: %s, %s, %s\n", e->rua, e->cidade, e->estado); } void imprimir_pessoa(const Pessoa *p) { printf("Nome: %s, Idade: %d\n", p->nome, p->idade); imprimir_endereco(&p->endereco); } int main() { Pessoa p = {"João", 30, {"Rua das Flores", "São Paulo", "SP"}}; imprimir_pessoa(&p); return 0; }#include <stdio.h> typedef struct { char nome[50]; int idade; } Pessoa; void saudar(Pessoa *p) { printf("Olá, meu nome é %s e tenho %d anos.\n", p->nome, p->idade); } typedef struct { Pessoa pessoa; char matricula[20]; } Estudante; void saudar_estudante(Estudante *e) { saudar(&e->pessoa); printf("Minha matrícula é: %s\n", e->matricula); } int main() { Estudante e = {{"João", 20}, "12345"}; saudar_estudante(&e); return 0; }
-
In Rust
-
Rust is a hybrid language .
-
It allows procedural code (functions, data manipulation) but also supports structs and traits, enabling OOP-like abstraction without classical inheritance or polymorphism.
-
Creating "objects" in Rust (
struct+impl) is hybrid: procedural logic with object-like composition. -
Objects in Rust :
-
Structs with methods are not true OOP objects; they use data and function composition .
-
-
Examples :
-
Hybrid: Procedural + "OOP" :
struct Carro { modelo: String, ano: i32, } impl Carro { fn novo(modelo: &str, ano: i32) -> Carro { Carro { modelo: modelo.to_string(), ano, } } fn detalhes(&self) { println!("Modelo: {}, Ano: {}", self.modelo, self.ano); } } fn main() { let carro1 = Carro::novo("Fusca", 1985); carro1.detalhes(); }-
Procedural :
fn maior(a: i32, b: i32) -> i32 { if a > b { a } else { b } } fn main() { let resultado = maior(10, 20); println!("O maior número é: {}", resultado); }struct Ponto { x: i32, y: i32 } fn distancia(p1: &Ponto, p2: &Ponto) -> f64 { let dx = p2.x - p1.x; let dy = p2.y - p1.y; ((dx * dx + dy * dy) as f64).sqrt() } fn main() { let p1 = Ponto { x: 3, y: 4 }; let p2 = Ponto { x: 7, y: 1 }; println!("Distância: {}", distancia(&p1, &p2)); }
-
In Zig
-
Zig is fundamentally procedural .
-
It lacks native OOP features like classes, inheritance, or polymorphism.
-
Structs :
-
No inheritance. Uses composition instead.
-
No constructors/destructors; manual init functions handle resource setup.
const std = @import("std"); const Pessoa = struct { nome: []const u8, idade: u32, pub fn init(nome: []const u8, idade: u32) Pessoa { return Pessoa{ .nome = nome, .idade = idade }; }, pub fn saudar(self: *Pessoa) void { std.debug.print("Olá, meu nome é {} e tenho {} anos.\n", .{ self.nome, self.idade }); }, }; pub fn main() void { var pessoa = Pessoa.init("João", 30); pessoa.saudar(); }
-
-
Examples :
-
Procedural :
const std = @import("std"); fn somar(a: i32, b: i32) i32 { return a + b; } pub fn main() void { const resultado = somar(10, 20); std.debug.print("A soma é: {}\n", .{resultado}); } -
"OOP" :
const std = @import("std"); const Falar = struct { falar: fn() void }; const Cachorro = struct { falar: Falar, nome: []const u8 }; const Gato = struct { falar: Falar, nome: []const u8 }; fn falar_cachorro() void { std.debug.print("Au Au!\n", .{}); } fn falar_gato() void { std.debug.print("Miau!\n", .{}); } pub fn main() void { var cachorro = Cachorro{ .falar = Falar{ .falar = falar_cachorro }, .nome = "Rex" }; var gato = Gato{ .falar = Falar{ .falar = falar_gato }, .nome = "Whiskers" }; cachorro.falar.falar(); gato.falar.falar(); }
-
In Go
-
Go is strongly procedural with optional abstraction through interfaces.
-
No classes, inheritance, or traditional polymorphism.
-
Examples :
-
Procedural :
package main import "fmt" func somar(a, b int) int { return a + b } func main() { fmt.Println("Resultado:", somar(10, 20)) } -
"OOP" :
package main import "fmt" type Saudável interface { Saudar() } type Pessoa struct { Nome string; Idade int } func (p *Pessoa) Saudar() { fmt.Println("Olá,", p.Nome) } type Animal struct { Nome string } func (a *Animal) Saudar() { fmt.Println("Roar! Eu sou", a.Nome) } func main() { var s Saudável pessoa := &Pessoa{"João", 30} animal := &Animal{"Leão"} s = pessoa; s.Saudar() s = animal; s.Saudar() }
-
In Lua
-
Lua can be used procedurally but can simulate OOP via tables + metatables .
-
Examples :
-
Procedural :
function somar(a, b) return a + b end print("Resultado: " .. somar(5, 10)) -
"Simulated OOP" :
Pessoa = {}; Pessoa.__index = Pessoa function Pessoa:new(nome, idade) local self = setmetatable({}, Pessoa) self.nome, self.idade = nome, idade return self end function Pessoa:saudar() print("Olá, meu nome é " .. self.nome .. " e tenho " .. self.idade .. " anos.") end Aluno = setmetatable({}, Pessoa) Aluno.__index = Aluno function Aluno:new(nome, idade, matricula) local self = setmetatable(Pessoa:new(nome, idade), Aluno) self.matricula = matricula return self end function Aluno:saudar() print("Sou aluno " .. self.nome .. " e minha matrícula é " .. self.matricula) end local pessoa1 = Pessoa:new("João", 30) local aluno1 = Aluno:new("Maria", 20, "12345") pessoa1:saudar() aluno1:saudar()
-
Functional
-
Examples :
-
Haskell, Lisp, Erlang, F#, Scala.
-
Summary of everything important
-
-
Explains from the start what FP is.
-
Keywords:
-
Separation of data and behaviour.
-
Types are just a set of data, not classes.
-
{17:06 -> 20:13}
-
Excellent explanation of Total Functions .
-
The input and output types document the function.
-
Either create a special input type, or expand the output to be an Option .
-
Do not lie and simply have exceptions inside the function.
-
-
"Use static types for domain modelling and documentation"
-
"Sorry Closure and JavaScript, not allowed".
-
-
-
{40:54 -> 49:43}
-
Explanation of the "Pyramid of Doom" problem
-
Introduction to Monads .
-
Explains Binds / Monadic Binds .
-
Applied to Error Handling and Async, usually.
-
"Railway Oriented Programming".
-
This stuff really seems very useful. It becomes very clear how to handle errors.
-
-
{49:44 -> 53:06}
-
Maps .
-
.map
-
"Lifting".
-
"World of options" and "world of normal values".
-
"Most wrapper generic types have a .map, use it!".
-
"Mappable types are Functors "; that's just mathematical jargon.
-
-
{53:07 -> }
-
Monoids .
-
"A pairwise operation has become an operation that works on lists".
-
-
-
-
The main concept is immutability.
-
As a consequence, functions end up being 'pure'.
-
They do not cause side effects.
-
They are deterministic (always return the same value for the same arguments).
-
-
-
You do not manipulate an object, but replace it with a new object produced from the previous object's data.
-
"Whenever there is a need to change a value, we create new objects based on the old one. This is important because immutability means you cannot directly modify an object's state; you create a new object with the changed values."
-
Possible problems
Monads
-
Chaining continuations.
-
Immutability :
-
Preference for:
-
tuples.
-
structs.
-
immutable objects.
-
-
-
Classes :
-
Using classes is not a common practice since FP emphasizes pure functions, immutability and composition rather than encapsulation and inheritance common in OOP. However, classes are not impossible or forbidden in FP. They can be used but normally in a way that does not contradict core FP principles.
-
Using classes immutably :
// Class representing a 2D point class Point { constructor(public readonly x: number, public readonly y: number) {} // Method that returns a new Point with changed values (immutability) move(dx: number, dy: number): Point { return new Point(this.x + dx, this.y + dy); } } // Using the class immutably const p1 = new Point(0, 0); const p2 = p1.move(10, 5); console.log(p1); // Point { x: 0, y: 0 } console.log(p2); // Point { x: 10, y: 5 }-
This is allowed because:
-
The
Pointclass is immutable due toreadonlyproperties. -
The
movemethod does not change the original object's state but returns a new object with modified values, respecting FP immutability.
-
-
-
Using classes to encapsulate functional behavior :
-
Without breaking pure functions:
class Calculator { constructor(private readonly value: number) {} // Operations that return new instances, without altering original state add(x: number): Calculator { return new Calculator(this.value + x); } sub(x: number): Calculator { return new Calculator(this.value - x); } result(): number { return this.value; } } // Using the class functionally (immutability) const c1 = new Calculator(10); const c2 = c1.add(5); // Does not change c1 const c3 = c2.sub(2); // Does not change c2 console.log(c1.result()); // 10 console.log(c2.result()); // 15 console.log(c3.result()); // 13 -
Breaking purity (therefore not 100% functional):
-
The
Calculatorclass encapsulates behavior that performs calculations. -
Immutability can be kept here because the class can be used as an immutable object (creating new values instead of modifying state).
-
Goes against pure functions:
-
The behavior of modifying the value internally goes against pure functions because
addmutates the object's state directly.
-
class Calculator { private value: number; constructor(initial: number) { this.value = initial; } add(x: number): Calculator { this.value += x; return this; // allows chaining } sub(x: number): Calculator { this.value -= x; return this; } result(): number { return this.value; } } // Using the class for chained calculations const calc = new Calculator(10); const result = calc.add(5).sub(2).result(); console.log(result); // 13 -
-
-
Concepts
-
First-class Functions
-
In functional languages, functions can be treated as values.
-
You can assign functions to variables, pass them as arguments, and return them as results.
-
-
Immutability
-
Data is treated as immutable, meaning it cannot be changed after creation.
-
Modifying data requires creating a new copy with the desired changes.
-
Only constants and functions using that constant as input are used.
-
Pure Functions
-
A pure function is deterministic: for the same inputs it always produces the same outputs, without altering global state or producing side effects.
-
This makes reasoning about code and parallelization easier.
-
The function output should depend only on inputs.
-
Nice, cool.
-
-
-
Function Composition
-
Functions can be combined to form other functions, promoting modularity and code reuse.
-
-
Recursion instead of Loops
-
Since mutability is discouraged, loops like
fororwhileare replaced by recursion.
-
-
Expressions, not Statements
-
Everything in functional languages is an expression that returns a value rather than a statement that changes state.
-
OOP (Object-Oriented Programming)
Thoughts
-
(2025-01-24) A public method should not return information about an internal node.
-
Avoid this:
func get_inventario_runtime() -> Array[Dictionary]: return _grid_slots.get_inventario_runtime()
-
About
-
Focuses on objects, which are instances of classes, and the interactions between those objects. State and behavior are encapsulated inside objects.
-
It is a paradigm but can be used as a basis for architectures.
Concepts
-
Encapsulation :
-
Association with SOLID :
-
Single-responsibility principle.
-
Dependency inversion principle.
-
-
"Encapsulation prevents external code from being concerned with the internal workings of an object."
-
"The best functions are those with no parameters".
-
-
Inheritance :
-
Association with SOLID :
-
Liskov substitution principle.
-
Open-closed principle.
-
-
When to use Inheritance (Hierarchical Reuse and Subtype Polymorphism) .
-
You can use Inheritance, but only when you want both Hierarchical Reuse of Code and Subtype Polymorphism.
-
{Only Hierarchical Reuse of Code}
-
If you don't plan to override any parent class behavior, using a Parent Class makes no sense; use Composition instead.
-
"Has a" vs "Is a" -> Composition over Inheritance.
-
-
{Only Subtype Polymorphism}
-
If you only plan to override a method of the Parent Class, it makes no sense for it to be a Parent Class; use an Interface.
-
-
{Avoid Inheritance}
-
"Should one always use Inheritance when wanting both Hierarchical Reuse of Code and Subtype Polymorphism? No!"
-
The presenter prefers using Interfaces for Subtype Polymorphism and Composition for Code Reuse.
-
-
Great video.
-
-
Virtuals: Using 'super()' to reproduce the original function content {8:00} .
-
"Functions in extending classes replace functions with the same name in their super classes. If you still want to call them, you can use
super()".
-
-
Difference between Class Inheritance and Interface Implementation:
-
Both are forms of "inheritance", but the first is Concrete while the second is Abstract.
-
The syntax is exactly the same in C#, but the word Implementation is used for Interfaces. It's a convention to differentiate Concrete behavior from Abstract behavior.
-
-
-
Polymorphism :
-
Association with SOLID :
-
Liskov substitution principle.
-
Dependency inversion principle.
-
-
"Many forms".
-
Subtype Polymorphism:
-
Allows removing conditional logic (if/else and match) via classes that represent subtypes of the class they "inherit".
-
Subtype Polymorphism can be achieved via Class Inheritance or Interface Implementation. Both are forms of "inheritance", but the first is Concrete while the second is Abstract.
-
When to use and not use Subtype Polymorphism .
-
The video's proposal differentiates 'data variations' vs 'behavior variations', so for the first use Classes, for the second use Interfaces.
-
The first example is interesting.
-
In the 3rd example, 'IMove' is better translated to 'IAbility'.
-
-
-
-
Abstraction :
-
Association with SOLID :
-
Interface segregation principle.
-
Dependency inversion principle.
-
-
Hides internal implementation details, exposing only essential functionality. This facilitates creating modular and maintainable systems.
-
Good Practices
-
-
Good channel.
-
-
SOLID :
-
Single-responsibility principle :
-
"There should never be more than one reason for a class to change. In other words, every class should have only one responsibility."
-
-
Open-closed principle :
-
"Software entities ... should be open for extension, but closed for modification."
-
-
Liskov substitution principle :
-
"Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it."
-
a.k.a. "whatever the parent can do, the descendants should at least be able to do that".
-
The Square-Rectangle Problem .
-
It's an interesting problem about whether Square or Rectangle should be Super-type or Sub-type; the answer is: they should not be related.
-
Nice video.
-
-
Liskov Substitution Principle.-
I didn't like the video. It's super technical without adding much. 'Type Rules' and 'Behavior Rules' are very similar, everything is based on Contravariance and Covariance.
-
-
-
Interface segregation principle :
-
"Clients should not be forced to depend upon interfaces that they do not use."
-
-
Dependency inversion principle :
-
"Depend upon abstractions, not concretes."
-
Related to the Subtype Polymorphism video.
-
-
Interface vs Abstract Class
-
About abstraction:
-
The fundamental difference between an abstract class and an interface is that an abstract class can contain both concrete (implemented) and abstract (unimplemented) methods, while an interface can only contain abstract method signatures. This matters because an abstract class can provide default implementations that subclasses inherit. An interface only defines method signatures that implementing classes must provide, without any implementation.
-
-
About constructors:
-
An abstract class can have constructors, fields, and instance methods.
-
Interfaces are contracts that specify the methods a class must implement, but they do not contain implementations or internal state like fields or constructors.
-